/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package fr.loof.fonzie.parsers;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.loof.fonzie.PersistenceQuery;
import fr.loof.fonzie.QueryParser;
import fr.loof.fonzie.keywords.After;
import fr.loof.fonzie.keywords.AfterEquals;
import fr.loof.fonzie.keywords.Aggregate;
import fr.loof.fonzie.keywords.And;
import fr.loof.fonzie.keywords.Before;
import fr.loof.fonzie.keywords.BeforeEquals;
import fr.loof.fonzie.keywords.Between;
import fr.loof.fonzie.keywords.Desc;
import fr.loof.fonzie.keywords.Equals;
import fr.loof.fonzie.keywords.GreaterEquals;
import fr.loof.fonzie.keywords.GreaterThan;
import fr.loof.fonzie.keywords.GroupBy;
import fr.loof.fonzie.keywords.In;
import fr.loof.fonzie.keywords.Join;
import fr.loof.fonzie.keywords.JoinFetch;
import fr.loof.fonzie.keywords.LessEquals;
import fr.loof.fonzie.keywords.LessThan;
import fr.loof.fonzie.keywords.Like;
import fr.loof.fonzie.keywords.Not;
import fr.loof.fonzie.keywords.NotEquals;
import fr.loof.fonzie.keywords.NotNull;
import fr.loof.fonzie.keywords.Null;
import fr.loof.fonzie.keywords.OrderBy;

/**
 * @author <a href="nicolas@apache.org">Nicolas De Loof</a>
 */
public class KeywordParser
    implements QueryParser
{
    private static Logger logger = LoggerFactory.getLogger( JPQLPersistenceQuery.class );

    private static KeyWord EQUALS = new Equals();

    private List<KeyWord> keywords = new LinkedList<KeyWord>();
    {
        keywords.add( EQUALS );
        keywords.add( new NotEquals() );
        keywords.add( new GreaterThan() );
        keywords.add( new LessThan() );
        keywords.add( new GreaterEquals() );
        keywords.add( new LessEquals() );
        keywords.add( new Null() );
        keywords.add( new NotNull() );
        keywords.add( new Between() );
        keywords.add( new Before() );
        keywords.add( new BeforeEquals() );
        keywords.add( new After() );
        keywords.add( new AfterEquals() );
        keywords.add( new Like() );
        keywords.add( new In() );

        keywords.add( new Not() );
        keywords.add( new And() );
        keywords.add( new OrderBy() );
        keywords.add( new Desc() );
        keywords.add( new Join() );
        keywords.add( new JoinFetch() );
        keywords.add( new GroupBy() );

        Collections.sort( keywords );
    }

    public PersistenceQuery parse( Class entityClass, Aggregate aggregate, String criteria, Class[] types )
    {
        logger.debug( "parse {} on {}", criteria, entityClass );

        List<QueryElement> elements = parseQuery( criteria );
        elements = postProcess( elements );
        logger.debug( "parsed query {}", elements );

        JPQLPersistenceQuery jpqlQuery = new JPQLPersistenceQuery( entityClass, aggregate );
        List<Class> parameterTypes = new LinkedList<Class>( Arrays.asList( types ) );

        for ( QueryElement queryElement : elements )
        {
            queryElement.process( jpqlQuery, parameterTypes );
        }

        return jpqlQuery;
    }

    private List<QueryElement> parseQuery( String criteria )
    {
        List<QueryElement> elements = new LinkedList<QueryElement>();
        int l = criteria.length();
        int position = 0;
        for ( int i = 0; i < l; i++ )
        {
            if ( Character.isLowerCase( criteria.charAt( i ) ) )
                continue;

            for ( KeyWord keyWord : keywords )
            {
                if ( criteria.startsWith( keyWord.getKeyword(), i ) )
                {
                    int next = i + keyWord.getKeyword().length();
                    if ( criteria.length() > next && Character.isLowerCase( criteria.charAt( next ) ) )
                    {
                        // next char is not uppercase, this is not a keyword but part of an identifier
                        continue;
                    }

                    if ( position < i )
                    {
                        elements.add( new Property( criteria.substring( position, i ) ) );
                    }
                    elements.add( keyWord );
                    position = next;
                    i = next - 1;
                    break;
                }
            }
        }
        if ( position < l )
            elements.add( new Property( criteria.substring( position ) ) );

        return elements;
    }

    /**
     * @param elements
     */
    private List<QueryElement> postProcess( List<QueryElement> elements )
    {
        for ( ListIterator<QueryElement> iterator = elements.listIterator(); iterator.hasNext(); )
        {
            QueryElement element = iterator.next();
            if ( !element.postProcess( iterator ) )
            {
                break;
            }
        }
        return elements;
    }

    public void setCustomKeyWords( List<KeyWord> keyWords )
    {
        keyWords.addAll( keyWords );
    }


}
